package com.gy.boot.common.aspect;

import cn.hutool.core.lang.Console;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.gy.boot.common.dao.IDao;
import com.gy.boot.common.dao.IEntity;
import com.gy.boot.common.dao.SqlID;
import com.gy.boot.common.exception.SysException;
import com.gy.boot.common.util.BlankUtil;
import com.gy.boot.common.util.HttpContextUtil;
import com.gy.boot.common.util.R;
import com.gy.boot.common.util.StrTool;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Description: HTTP请求拦截
 * 面向切面: AOP
 * 注解: @Component : 将类注入到Spring容器中;
 * 注解: @Aspect :  声明AOP处理
 * 注解: @Pointcut :  声明拦截地址
 * 注解: @Before : 在Value中的方法  之前  都进行拦截
 * 注解: @After : 在Value中的方法  之后  都进行拦截
 * 注解: @AfterReturning : 在Value中的方法  之后  都进行拦截,并获取返回的数据
 * 注解: @Around : 环绕拦截, 从进入方法前, 到进入方法后, 都进行环绕处理
 **/
@Aspect
@Component
public class ControllerAspect {
    private static Logger logger = LoggerFactory.getLogger(ControllerAspect.class);

    /** 通过地址-全局拦截  */
    @Pointcut("execution(public * com.gy.boot.controller..*.*(..))")
    public void controllerAll() {}

    /** 注解拦截 */
    @Pointcut("@annotation(com.gy.boot.common.aspect.Log)")
    public void log() {}

    /** 关机借口 */
    private static final String shutdownServletPath = "/login/shutdown";

    /** 环绕式拦截, 进入方法前拦截, 出方法后拦截, 可以获取到传入参数和返回值 */
    @Around("controllerAll()")
    public Object aroundControllerAll(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        Signature signature = joinPoint.getSignature();
        String classMethod = signature.getDeclaringTypeName()+"."+signature.getName()+"()"; // 类方法名称
        Method method = ((MethodSignature) signature).getMethod();
        String logValue = getLogValue(method);  // 日志描述
        String clientIP = ServletUtil.getClientIP(request); // IP
        Map<String, String> paramMap = ServletUtil.getParamMap(request);  // 参数
        Long user_id = HttpContextUtil.getJwtUserId(request);
        String servletPath = request.getServletPath(); // 接口地址
        // 校验接口权限
        boolean isPermissions = checkPermissions(method, servletPath); // 校验接口权限
        // IP; 请求方式; 请求地址; 用户ID; 类方法名; 参数集合
        logger.info("ip: {}; url: {}; method: {}; \nclassMethod: {}; \nparams: {}; \nuser_id: {}; logValue: {}; isPermissions: {}"
                , clientIP, request.getRequestURL(), request.getMethod(), classMethod, paramMap, user_id, logValue, isPermissions);
        if (!isPermissions) {
            throw new SysException("您还没有该访问权限, 请联系管理员授予!", 402);
        }
        if (shutdownServletPath.equals(servletPath)) { // 停机记录
            saveLog(clientIP, request.getMethod(), servletPath, classMethod, 3000, paramMap, logValue, user_id);
        }
        long beginTime = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 执行方法
        long time = System.currentTimeMillis() - beginTime; // 执行时长(毫秒)
        logger.info("执行时长: " + time + "ms");
        // 解析返回结果,获取执行状态码
        if (logValue != null && result instanceof R) {
            R r = (R) result;
            boolean success = r.getBo("success");
            if (success) { // 保存日志到数据库
                saveLog(clientIP, request.getMethod(), servletPath, classMethod, time, paramMap, logValue, user_id);
            }
        }
        return result;
    }

    /**
     * 记录日志到数据库
     * @param clientIP 请求IP
     * @param classMethod 类方法名
     * @param time_length 请求时长
     * @param logValue 日志说明
     * @param userId 操作人ID
     * */
    private void saveLog(String clientIP, String method, String servletPath, String classMethod
            , long time_length, Map<String, String> paramMap, String logValue, Long userId) {
        Console.log("保存日志到SQL: {}; {}; {}; {}; {}; {};", clientIP, classMethod, time_length, paramMap, logValue, userId);
        String params = BlankUtil.isBlank(paramMap) ? null : JSONUtil.toJsonStr(paramMap);
        IEntity logParams = IEntity.create(8);
        logParams.set("user_id", userId);
        logParams.set("content", logValue);
        logParams.set("client_ip", clientIP);
        logParams.set("method", method);
        logParams.set("servlet_path", servletPath);
        logParams.set("class_method", classMethod);
        logParams.set("params", params);
        logParams.set("time_length", time_length);
        IDao.insertS(SqlID.SysLog_insertLog, logParams);
    }

    /**  获取日志注解中的值(记录说明) */
    private String getLogValue(Method method) {
        Log sysLog = method.getAnnotation(Log.class);
        if (sysLog == null) { return null; }
        return sysLog.value();
    }

    /** 校验接口权限-当前用户是否可访问该页面 */
    private boolean checkPermissions(Method method, String requestServletPath) {
        Permissions permissions = method.getAnnotation(Permissions.class);
        if (permissions == null) { // 判断该方法是否有权限注解
            Class<?> declaringClass = method.getDeclaringClass();
            permissions = declaringClass.getAnnotation(Permissions.class);
            if (permissions == null) {
                return true; // 没有可直接访问
            }
        }
        // 进行权限校验
        String permissionsValue = StrTool.removePathSlash(requestServletPath);
        // TODO 用户登录时缓存的用户接口权限码进行对比; 对比通过放行
        List<String> listPermissionsValue = listPermissionsValue();
        return listPermissionsValue.contains(permissionsValue);
    }

    /**
     * TODO 用户登录的所有权限码, 可通过数据库获取
     * */
    private List<String> listPermissionsValue() {
        List<String> listPermissionsValue = new ArrayList<>();
        listPermissionsValue.add("test/permissions");
        return listPermissionsValue;
    }

}
